package com.livestocksolutions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.TreeMap;

import com.livestocksolutions.interfaces.Observable;
import com.livestocksolutions.util.UpdateHistory;

/* -- Version History --
 *  1.0 : ???          - Initial version.
 *  
 *  1.1 : 14 September 2012 - Added protected constructor Bull(long id), to be used when changing Gender
 *                       from Unknown.
 *  
 *  1.2 : 22 September 2012 - Added Update methods to merge values from the same Objects that existed on
 *                       different systems. 
 *                       Serialisation method no longer requires MedicalHistory implementing Serializable.
 *                       
 *  1.3 : 14 October 2012 - Added #getMedicalHistoryByID(long) to get the MedicalHistory with that given ID.
 *  
 *  1.4 25 October 2012 - Added #getAverageDailyGrowth() to calculate the entire growth of an animal from
 *                       birth or first weighing to current weighing.
 *                                        
 *  1.5 25 October 2012 - Added #getOffspringAverageDailyGrowth() to calcualte the average growth of the bovines offspring
 *  						added #getStringStatus() to return the status in string type for easier output 
 *  
 * 1.6 25 October 2012 - Added #getOffspringAverageSellPrice() to calculate the average price that the bovines offspring were sold for, 
 * 							ignores bovine not yet sold and deceased.
 */

/**
 * Bovine class represents a bovine (cattle) in the real world
 * This class provides the base for the Cow and Bull classes.
 * @author Matt Lang
 * @author Adam Parr
 * @version 1.4
 * 
 * @.modified 28 October 2012
 */
public class Bovine implements Observable{
	/**
	 * A String that identifies any Object of this type as being of this sex
	 */
	public static final String GENDER = "Unknown";	
	/**
	 * Keep track of any changes, until this Bovine is assigned to an owner.
	 */
	protected HashMap<String,UpdateHistory> update = new HashMap<String,UpdateHistory>();
	
	/**
	 * Internal Unique ID of Bovine
	 */
	protected final long id;
	
	/**
	 * represents the bovines date of birth
	 */
	protected long dob;
	
	/**
	 * represents whether the cow is active (true means live and in current paddocks)
	 */
	protected boolean isActive = true;
	
	/**
	 * represents a Bovines gender
	 */
	protected String gender;
	
	/**
	 * represents the eartag on the bovine
	 */
	protected Eartag eartag;
	
	
	/**
	 * the sire of the bovine (father)
	 */
	protected long sire;
	
	/**
	 * the dame of the bovine (mother)
	 */
	protected long dame;
	
	/**
	 * An Object that stores how this Bovine was acquired
	 */
	protected Acquisition acquisition;
	
	/**
	 * Cattle Breed of the Bovine
	 */
	protected long breed;
	
	/**
	 * An Object that stores how this Bovine is no-longer an active Bovine
	 */
	protected Deacquisition deacquisition;
	
	/**
	 * contains the mob that the bovine is currently apart of
	 */
	protected long belongingMob;
	
	/**
	 * contains an arraylist of mobs that the bovine has previously been apart of
	 */
	protected HashMap<Long,ArrayList<GroupHistory>> mobHistory = new HashMap<Long,ArrayList<GroupHistory>>();
	
	/**
	 * contains an arraylist of the groups that the bovine has previously been apart of
	 */
	protected HashMap<Long,ArrayList<GroupHistory>> groupHistory = new HashMap<Long,ArrayList<GroupHistory>>();
	
	/**
	 * offspring arraylist contains the offspring(bovine object) of the bovine
	 */
	protected ArrayList<Long> offspring = new ArrayList<Long>();
	
	/**
	 * Arraylist of groups that this bovine belongs to
	 */
	protected ArrayList<Long> groups = new ArrayList<Long>();
	
	/**
	 * Arraylist that contains observations made about the bovine
	 */
	protected ArrayList<Long> observations = new ArrayList<Long>();
	
	/**
	 * array of the joinings that this bovine has been apart
	 */
	protected ArrayList<Long> joinings = new ArrayList<Long>();
	/**
	 * arraylist contains feed given to the bovine
	 */
	protected HashMap<Long,FeedHistory> handFeeds = new HashMap<Long,FeedHistory>();
	
	/**
	 * contains medicines given to the bovine (medicine is a dosage of something)
	 */
	protected HashMap<Long,MedicineHistory> medicines = new HashMap<Long,MedicineHistory>();
	
	/**
	 * contains medicals given to the bovine (medicals are procedures done on the bovine)
	 */
	protected HashMap<Long,MedicalHistory> medicals = new HashMap<Long,MedicalHistory>();
	
	/**
	 * Contains an arraylist of weighings of the bovine
	 */
	protected TreeMap<Long,Weight> weighings = new TreeMap<Long,Weight>();
	
	/**
	 * represents the date at which it is safe to slaughter the bovine
	 */
	protected long safeSlaughterDate;
	
	/**
	 *Constructor to initiate class passing the date of birth as the initial value
	 * @param dob The date of birth for this bovine
	 */
	public Bovine(Date dob)
	{
		this.dob = dob!=null?dob.getTime():0;
		gender = GENDER;
		
		long tempID = Calendar.getInstance().getTimeInMillis();
		while(LivestockSolutions.getBovineByID(tempID)!=null){
			// Just in case multiple items of this type are made in the same millisecond
			tempID++;
		}
		id=tempID;
	}//end constructor
	
	/**
	 * To be used when changing the gender of the Bovine.<br>
	 * Note: This will only be valid when the original Type is Bovine, with the intended Type
	 * being one of the sub classes.
	 * @param id The ID of the Bovine in which to replace.
	 */
	protected Bovine(long id){
		this.id=id;
	}
	
	/**
	 *Accessor for the id variable
	 *@return id - unique identifier of each bovine
	 */
	public long getID()
	{
		return id;
	}//end getID
	
	/**
	 *Accessor for the gender variable
	 *@return The Gender in human readable String format.
	 */
	public String getGender()
	{
		return gender;
	}//end getGender
	
	/**
	 *Accessor for the date of birth object
	 *@return dob - date of birth of the bovine
	 */
	public Date getDob()
	{
		return dob==0?null:new Date(dob);
	}//end getDob
	
	/**
	 * Check to see if this Bovine is currently active on the farm, or has been sold or died.
	 * @return <tt>true</tt> if the bovine is still active, <tt>false</tt> otherwise.
	 */
	public boolean isActive()
	{
		return isActive;
	}//end getIsActive
	
	/**
	 *Accessor for the bovine's Eartag object
	 *@return eartag -  contains information about the eartag on the bovine
	 */
	public Eartag getEartag()
	{
		return eartag;
	}//end getEartag
	
	/**
	 *Accessor for the cows sire
	 *@return sire
	 */
	public Bull getSire()
	{
		return (Bull)LivestockSolutions.getBovineByID(sire);
	}//end getSire
	
	/**
	 *Accessor for the cows dame
	 * @return Returns the mother of this Bovine or <tt>null</tt> if the Cow does not exist
	 * in the active data set.
	 */
	public Cow getDame()
	{
		return (Cow)LivestockSolutions.getBovineByID(dame);
	}//end getDame
	
	/**
	 *Accessor for the offspring of the Bovine
	 *@return offspring
	 */
	public Bovine[] getOffspring()
	{
		Bovine[] tmp = new Bovine[offspring.size()];
		
		for(int i = 0; i < offspring.size(); i++)
		{
			tmp[i] = LivestockSolutions.getBovineByID(offspring.get(i));
		}
		return tmp;
	}//end getOffspring
	
	/**
	 *Accessor for the breed object
	 *@return breed - contains information about the breed of bovine
	 */
	public CattleBreed getBreed()
	{
		return LivestockSolutions.getCattleBreedByID(breed);
	}//end getBreed
	
	/**
	 * Accessor for the groups object
	 * @return groups - groups that the bovine is currently apart of
	 */
	public Group[] getGroups()
	{
		Group[] tmp = new Group[groups.size()];
		for (int i = 0; i < groups.size(); i++){
			tmp[i] = LivestockSolutions.getGroupByID(groups.get(i));
		}
		return tmp;
	}
	/**
	 *Accessor for the acquisition object
	 *@return acquisition - how the bovine was acquired
	 */
	public Acquisition getAcquisition()
	{
		return acquisition;
	}//end getAcquisition
	
	/**Accessor for the deacquisition object
	 *@return deacquisition - how the bovine was deacquired (sold/died/slaughtered)
	 */
	public Deacquisition getDeacquisition()
	{
		return deacquisition;
	}//end getDeacquisition
	
	/**
	 *Accessor to return recorded weighings, this method creates an array from the arraylist and returns the array.
	 *@return weighings converted to an array
	 */
	public Weight[] getWeighings()
	{
		return	weighings.values().toArray(new Weight[]{});
	}//end getWeighings
	
	/**
	 *Accessor for the belongingmob attribute
	 *@return belongingMob
	 */
	public Mob getBelongingMob()
	{
		return (Mob)LivestockSolutions.getGroupByID(belongingMob);
	}//end getBelongingMob
	
	/**
	 *Method creates an array of observations from the observations arraylist.
	 *@return array of observations
	 */
	@Override
	public Observation[] getObservations()
	{
		int size;
		Observation[] ob = new Observation[size = observations.size()];
		for (int x=0; x<size; x++) {
			ob[x] = LivestockSolutions.getObservationByID(observations.get(x));
		}
		return ob;
	}//end getObservations
	
	@Override
	public boolean removeObservation(Observation observation) {
		long oi=observation.getID();
		boolean value=observations.remove(oi);
		update.get("OBSERVATION").getValue(String.valueOf(observation.getID())).setValue(String.valueOf(!value));
		return value;
	}
	
	/**
	 * Accessor for the groupHistory object
	 * @return - groupHistory previous groups the bovine has been in
	 */
	public GroupHistory[] getGroupHistory()
	{
		return groupHistory.values().toArray(new GroupHistory[]{});
	}//end groupHistory
	
	/**
	 *Method creates an array of FeedHistory objects to be returned. FeedHistory can be any feed that has been given to a Bovine.
	 *@return array of feed objects
	 */
	public FeedHistory[] getHandFeeds()
	{
		return handFeeds.values().toArray(new FeedHistory[]{});
	}//end getHandFeeds
	
	/**
	 *Method creates an array of medicines given to the Bovine
	 *@return array of medicineHistory
	 */
	public MedicineHistory[] getMedicines()
	{
		return medicines.values().toArray(new MedicineHistory[]{});
	}//end getMedicines
	
	/**
	 *Method creates an array of Medical procedures given to the Bovine
	 *@return array list of medical procedures
	 */
	public MedicalHistory[] getMedicals()
	{
		return medicals.values().toArray(new MedicalHistory[]{});
	}//end getMedicals
	
	/**
	 * Get a medical history object with the given id.
	 * @param medicalHistoryID The id of the Medical History to get.
	 * @return <tt>MedicalHistory</tt> when there is a MedicalHistory with this ID,
	 *          <tt>null</tt> if there is no MedicalHistory with that ID.
	 */
	public MedicalHistory getMedicalHistoryByID(long medicalHistoryID){
		return medicals.get(medicalHistoryID);
	}
	
	/**
	 *Accessor for the safe slaughter date object
	 *@return safeSlaughterDate: Date object containing the date at which it is safe to slaughter this bovine
	 */	
	public Date getSafeSlaughterDate()
	{
		return safeSlaughterDate==0?null:new Date(safeSlaughterDate);
	}//end getSafeSlaughterDate
	
	/**
	 *Method creates and returns an array list of joinings of a certain Bovine
	 *@return array of joinings relative to this bovine
	 */ 
	public Join[] getJoinings()
	{
		Join[] tmp = new Join[joinings.size()];
		
		for (int i = 0; i < joinings.size(); i++)
		{
			tmp[i] = LivestockSolutions.getJoiningByID(joinings.get(i));
		}
		return tmp;
	}//end getJoinings
	
	/**
	 * Mutator for the breed attribute
	 * @param breed - CattleBreed of the bovine to be written to the bovine object
	 */
	public void setBreed(CattleBreed breed){
		if(breed!=null&&breed.getID()!=this.breed){
			this.breed = breed.getID();
			update.put("BREED", new UpdateHistory(String.valueOf(this.breed)));
		}
		
	}//end setBreed
	
	/**
	 *Mutator to set the DOB object checks if date object passed is after todays date
	 *if so will not allow the change
	 *@param dob The new date of birth object to be written
	 * @return <tt>true</tt> if the dob was successfully updated, <tt>false</tt> otherwise.
	 */
	public boolean setDOB(Date dob)
	{
		//check if dob(date of birth) @param is after current date, if so will not allow it to be updated for obvious reasons.
		if(dob !=null && !dob.after(new Date())){
				this.dob = dob.getTime();
				if(acquisition!=null&&acquisition.getMethod().toLowerCase().equals("birth")){
					// Update the Acquisition date to the DOB as this was a birth.
					acquisition=new Acquisition(acquisition.getMethod(), acquisition.getPrice(), acquisition.getOwner(), dob);
				}
				update.put("DOB", new UpdateHistory(String.valueOf(this.dob)));
				return true;
			}
		return false;
	}//end setDOB
	
	/**
	 * Set the Gender of a Bovine, when gender equals {@link #GENDER}
	 * @param subClass The Class of the new Bovine that will replace this
	 * @return The new instance of this object reflecting the gender change.
	 */
	public Bovine setGender(Class<? extends Bovine> subClass){
		if(gender.equals(Bovine.GENDER)){
			try {
				// Change this Bovine to a sub class version
				Bovine b = subClass.getDeclaredConstructor(long.class).newInstance(id);
				update.put("GENDER", new UpdateHistory(b.gender));
				b.acquisition=acquisition;
				b.dob=dob;
				b.isActive=isActive;
				b.eartag=eartag;
				b.sire=sire;
				b.dame=dame;
				b.offspring=offspring;
				b.acquisition=acquisition;
				b.breed=breed;
				b.deacquisition=deacquisition;
				b.weighings=weighings;
				b.belongingMob=belongingMob;
				b.mobHistory=mobHistory;
				b.groupHistory=groupHistory;
				b.groups=groups;
				b.observations=observations;
				b.handFeeds=handFeeds;
				b.medicines=medicines;
				b.medicals=medicals;
				b.safeSlaughterDate=safeSlaughterDate;
				b.joinings=joinings;
				b.update=update;
				return b;
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return this;
	}
	
	/**
	 *Mutator to set the Eartag object
	 *@param eartag - New Eartag object to be attached to the bovine
	 */
	public void setEartag(Eartag eartag)
	{
		eartag.setOwner(this);
		this.eartag = eartag;
	}//end setEartag
	
	/**
	 *Mutator for the Sire object (bovines father)
	 *Does validation to ensure object passed is a bull (male)
	 *@param sire Bull object to be  written as bovine's father
	 * @return <tt>true</tt> if the Bull given is of type {@link com.livestocksolutions.Bull}
	 * and was added successfully, <tt>false</tt> otherwise.
	 */
	public boolean setSire(Bull sire) {
		System.out.println("Adding Offspring "+this.sire);
		if(this.sire==0 && sire!=this && sire!=null) {
			this.sire=sire.getID();
			System.out.println("Adding Offspring 2");
			sire.addOffspring(this);
			update.put("SIRE", new UpdateHistory(String.valueOf(this.sire)));
			return true;
		}
		return false;
	}//end setSire
	
	/**
	 *Mutator for the dame object (bovines mother)
	 *Does validation to ensure object passed is a cow (female)
	 *@param dame Object of type Cow to replace the current one.
	 * @return <tt>true</tt> if the Cow given is of type {@link com.livestocksolutions.Cow}
	 * and was added successfully, <tt>false</tt> otherwise.
	 */
	public boolean setDame(Cow dame) {
		//checks that the object given is a cow (female)
		if(this.dame==0 && dame!=this && dame!=null) {
			this.dame = dame.getID();
			dame.addOffspring(this);
			
			update.put("DAME", new UpdateHistory(String.valueOf(this.dame)));
			return true;
		}
		return false;
	}//end setDame
	
	/**
	 *Mutator for the acquisition attribute
	 *@param acquisition - acquisition to replace current acquisition
	 */
	public void setAcquisition(Acquisition acquisition){
		if(this.acquisition==null){
			this.acquisition = acquisition;
		}
	}//end setAcquisition
	
	/**
	 *Mutator for the deacquisition object, isActive will be set to false due to obvious reasons
	 *@param deacquisition - replacement for the current deacquisition attribute
	 */
	public void setDeacquisition(Deacquisition deacquisition){
		if(this.deacquisition==null){
			this.deacquisition = deacquisition;
			isActive = false;
			// Remove this inactive Bovine from the mob it may be in.
			setMob(null);
			update.put("ISACTIVE", new UpdateHistory("false"));
		}
	}//end setDeacquisition
	
	/**
	 *Mutator for the safe slaughter date attribute
	 *@param date - to set the safe slaughter date
	 */
	public void setSafeSlaughterDate(Date date)
	{
		/* If the safeSlaughterDate is null or before the given date, set safeSlaughterDate to the current date,
		 * otherwise it remains the current value of safeSlaughterDate.
		 */
		safeSlaughterDate = safeSlaughterDate == 0 || new Date(safeSlaughterDate).before(date)? date.getTime() : safeSlaughterDate;
		
		update.put("SAFESLAUGHTER", new UpdateHistory(String.valueOf(safeSlaughterDate)));
	}//end setSafeSlaughterDate
	
	/**
	 * Move this Bovine from the current mob and into the new mob
	 * @param mob new mob to add this bovine to.
	 */
	public void setMob(Mob mob)
	{
		Date now = new Date();
		if((mob!=null && mob.getID()!=belongingMob) || mob==null){
			if(mobHistory.get(belongingMob)!=null){
				// Get the Mob this Bovine was last in
				GroupHistory oldMob = mobHistory.get(belongingMob).get(mobHistory.get(belongingMob).size()-1);
				if(oldMob.getExitDate()==null){
					// Set the date this Bovine was taken out of this Mob
					oldMob.setExitDate(now);
					// Remove this Bovine from the old Mob
					oldMob.getGroup().removeBovine(this);
					oldMob.setOwner(this);
				}
			}
			if(mob!=null){
				// Add this Bovine to the new Mob
				mob.addBovine(this);
				// Add this new Mob to the history
				ArrayList<GroupHistory> newMob = mobHistory.get(mob.getID());
				newMob=newMob==null?new ArrayList<GroupHistory>():newMob;
				GroupHistory gh = new GroupHistory(mob, now);
				gh.setOwner(this);
				newMob.add(gh);

				mobHistory.put(mob.getID(),newMob);
				belongingMob = mob.getID();
			}else{
				belongingMob=0;
			}
			
			update.put("BELONGINGMOB", new UpdateHistory(String.valueOf(belongingMob)));
		}
	}//end setMob
	
	/**
	 * Method adds an offspring to this bovine
	 * @param offspring offspring (bovine object) to be added to offspring array
	 * @return <tt>true</tt> if offspring is not null, and is not already an offspring,
	 * <tt>false</tt> otherwise.
	 */
	public boolean addOffspring(Bovine offspring)
	{
		//checks that the bovine is not already listed as an offspring	
		if(offspring!=null && !this.offspring.contains(offspring.getID())){
			//adds bovine then returns true to indicate successful
			this.offspring.add(offspring.getID());
			
			UpdateHistory offspringList = update.get("OFFSPRING")!=null? update.get("OFFSPRING"):new UpdateHistory();
			offspringList.setValues(String.valueOf(offspring.getID()),new UpdateHistory("true"));
			update.put("OFFSPRING", offspringList);
			return true;	
		}	
		return false;
	}//end addOffspring
	
	/**
	 *Method adds a weighing of a bovine to the weighings array
	 *@param weighing weighing to be added to the array
	 */
	public void addWeighing(Weight weighing) {
		weighings.put(weighing.getID(),weighing);
		
		UpdateHistory weightList = update.get("WEIGHING")!=null? update.get("WEIGHING"):new UpdateHistory();
		weightList.setValues(String.valueOf(weighing.getID()),new UpdateHistory("true"));
		update.put("WEIGHING", weightList);
	}//end addWeighing
	
	/**
	 * Add a new group to this Bovine.
	 * @param group The group to add.
	 * @return <tt>true</tt> if this bovine was added to this group. 
	 *          <tt>false</tt> if this Bovine is already a member.
	 */
	public boolean addGroup(Group group)
	{
		//checks that the bovine is not already listed as a current member	
		if(!groups.contains(group.getID())){
			ArrayList<GroupHistory> history = groupHistory.get(group.getID());
			
			history=history==null?new ArrayList<GroupHistory>():history;

			GroupHistory gh = new GroupHistory(group, new Date());
			history.add(gh);
			groupHistory.put(group.getID(), history);
			//adds bovine then returns true to indicate successful
			groups.add(group.getID());
			
			UpdateHistory groupList = update.get("GROUP")!=null? update.get("GROUP"):new UpdateHistory();
			groupList.setValues(String.valueOf(group.getID()),new UpdateHistory("true"));
			update.put("GROUP", groupList);
			group.addBovine(this);
			return true;
		}
		return false;
	}
	
	/**
	 *Method is to add an observation about the bovine
	 *@param observation observation about the bovine to be added
	 */
	public void addObservation(Observation observation) {
		if(!observations.contains(observation.getID())){
			observations.add(observation.getID());
			UpdateHistory observationList = update.get("OBSERVATION")!=null? update.get("OBSERVATION"):new UpdateHistory();
			observationList.setValues(String.valueOf(observation.getID()),new UpdateHistory("true"));
			update.put("OBSERVATION", observationList);
		}
		observation.setOwner(this);
		LivestockSolutions.addObservation(observation);
	}//end addObservation
	
	/**
	 *Method is to add a hand feed to the bovine object
	 *@param handFeed HandFeed object to be added to the array (contains a hand feed given to bovine)
	 */
	public void addHandFeed(FeedHistory handFeed){
		if(handFeed.getID()==0){
			// Adding a new Contact
			long newID = new Date().getTime();
			while(handFeeds.containsKey(newID)){
				newID++;
			}
			handFeed.setID(newID);
		}
		if(!handFeeds.containsKey(handFeed.getID())){
			handFeeds.put(handFeed.getID(),handFeed);
			
			UpdateHistory feedList = update.get("HANDFEED")!=null? update.get("HANDFEED"):new UpdateHistory();
			feedList.setValues(String.valueOf(handFeed.getID()),new UpdateHistory("true"));
			update.put("HANDFEED", feedList);
		}
	}
	
	/**
	 *Method to add a medicine given to a bovine into the medicine array
	 *@param medicine medicine that was given to the bovine to be added to the array
	 */
	public void addMedicine(MedicineHistory medicine){
		if(medicine.getID()==0){
			// Adding a new Contact
			long newID = new Date().getTime();
			while(medicines.containsKey(newID)){
				newID++;
			}
			medicine.setID(newID);
		}
		if(!medicines.containsKey(medicine.getID())){
			medicines.put(medicine.getID(),medicine);
			Calendar cal = Calendar.getInstance();
			cal.setTime(medicine.getDate());
			cal.add(Calendar.MINUTE, (int)(((Medicine)medicine.getItem()).getWithholdPeriod()/1000/60));
			setSafeSlaughterDate(cal.getTime());
			UpdateHistory medicineList = update.get("MEDICINE")!=null? update.get("MEDICINE"):new UpdateHistory();
			medicineList.setValues(String.valueOf(medicine.getID()),new UpdateHistory("true"));
			update.put("MEDICINE", medicineList);
		}
	}
	
	/**
	 * Method to add a medical procedure done on the bovine into the medical array
	 * @param medical the medical to be added to the array
	 */
	public void addMedical(MedicalHistory medical){
		if(medical.getID()==0){
			// Adding a new Contact
			long newID = new Date().getTime();
			while(medicals.containsKey(newID)){
				newID++;
			}
			medical.setID(newID);
		}
		if(!medicals.containsKey(medical)){
			medicals.put(medical.getID(),medical);
			
			for(MedicineHistory medicine : medical.getPrescribedMedicines()){
				Calendar cal = Calendar.getInstance();
				cal.setTime(medicine.getDate());
				cal.add(Calendar.MINUTE, (int)(((Medicine)medicine.getItem()).getWithholdPeriod()/1000/60));
				setSafeSlaughterDate(cal.getTime());
			}
			
			UpdateHistory medicalList = update.get("MEDICAL")!=null? update.get("MEDICAL"):new UpdateHistory();
			medicalList.setValues(String.valueOf(medical.getID()),new UpdateHistory("true"));
			update.put("MEDICAL", medicalList);
		}
	}
	
	/**
	 * Method to add a joining done with this bovine to the array
	 * @param newJoining the joining object the bovine was involved in that will be added to the array
	 * @return <tt>true</tt> If this Bovine is not already a member and was added successfully,
	 * <tt>false</tt> otherwise
	 */
	public boolean addJoining(Join newJoining) {
		if (!joinings.contains(newJoining.getID())){
			joinings.add(newJoining.getID());
			
			UpdateHistory joiningList = update.get("JOINING")!=null? update.get("JOINING"):new UpdateHistory();
			joiningList.setValues(String.valueOf(newJoining.getID()),new UpdateHistory("true"));
			update.put("JOINING", joiningList);
			return true;
		}
		return false;
	}//end addJoining
	
	/**
	 *Method to remove the bovine from a group
	 *This method is a copy of the addGroup just reversing the found actions and removing only if found
	 *@param group the group that the bovine will be removed from
	 * @return <tt>true</tt> if this Bovine was successfully removed from this group. <tt>false</tt> otherwise.
	 */
	public boolean removeGroup(Group group)
	{
		if(group.removeBovine(this)){
			//if bovine is found in the group will add bovine, create group history object and return true to indicate change was made
			ArrayList<GroupHistory> history = groupHistory.get(group.getID());
			GroupHistory gh = history.get(history.size()-1);
			gh.setExitDate(new Date());
			groupHistory.put(group.getID(), history);
			
			groups.remove(group.getID());
			
			UpdateHistory groupList = update.get("GROUP")!=null? update.get("GROUP"):new UpdateHistory();
			groupList.setValues(String.valueOf(group.getID()),new UpdateHistory("false"));
			update.put("GROUP", groupList);

			return true;
		}
		return false;
	}//end removeGroup
	
	/**
	 * Method calculates the weight gain of a bovine over all recorded weighings
	 * @return weightGain - calculated weight gain of the bovine
	 */
	public float calculateWeightGain()
	{
		if(!weighings.isEmpty())
		{
			Weight firstWeighing = weighings.get(0);
			Weight lastWeighing = weighings.get(0);
			for (Weight w : weighings.values())
			{
				if(firstWeighing.getDate().after(w.getDate()))
				{
					firstWeighing = w;
				}
			}
			for (Weight w : weighings.values())
			{
				if(lastWeighing.getDate().before(w.getDate()))
				{
					lastWeighing = w;
				}
			}
			return (float)(lastWeighing.getWeight() - firstWeighing.getWeight());
		}
		return (float) 0.00;
	}//end calculateWeightGain
	
	/**
	 * Overloaded method to supply start and end dates to search for weight gain over a particular period
	 * @param start - starting date from which to calculate weight gain
	 * @param end - end date from which to calculate weight gain
	 * @return weightGain - calculated weight gain of the bovine over given dates
	 */
	public float calculateWeightGain(Date start, Date end)
	{
		float weightGain=0;
		//checks to see if start is before end, if not switches the two around
		Date s=start,e=end;
		if(start.after(end))
		{
			s = end;
			e = start;
		}
		if(!weighings.isEmpty())
		{
			ArrayList<Weight> tempWeight = new ArrayList<Weight>();
			for (Weight w : weighings.values())
			{
				if(s.before(w.getDate()) && e.after(w.getDate()))
				{
					tempWeight.add(w);
					weightGain = (float)(weightGain + w.getWeight());
				}
			}
			if(!tempWeight.isEmpty())
			{
				Weight firstWeighing = tempWeight.get(0);
				Weight lastWeighing = tempWeight.get(0);
				for (Weight w : tempWeight)
				{
					if(firstWeighing.getDate().after(w.getDate()))
					{
						firstWeighing = w;
					}
				}
				for (Weight w : tempWeight)
				{
					if(lastWeighing.getDate().before(w.getDate()))
					{
						lastWeighing = w;
					}
				}
				return (float)(lastWeighing.getWeight() - firstWeighing.getWeight());
			}	
		}
		return (float) 0.00;
	}//end calculateWeightGain

	/**
	 * Updates this Object with the updated values from the different version.
	 * @param changes Bovine that represents this Object in a different version.
	 * @return <tt>true</tt> if changes is not null and changes does not equal this object, <tt>false</tt> otherwise.
	 */
	public boolean update(Bovine changes){
		if(changes!=null && this!=changes){
			// Simple Checks
			if(updateBefore("BREED",changes)){
				breed=Long.parseLong(changes.update.get("BREED").getValue());
				update.put("BREED", new UpdateHistory(String.valueOf(breed)));
			}
			
			if(updateBefore("DOB",changes)){
				dob=Long.parseLong(changes.update.get("DOB").getValue());
				update.put("DOB", new UpdateHistory(String.valueOf(dob)));
			}
			
			if(updateBefore("GENDER",changes)){
				gender=changes.update.get("GENDER").getValue();
				update.put("GENDER", new UpdateHistory(gender));
			}
			
			if(updateBefore("SIRE",changes)){
				sire=Long.parseLong(changes.update.get("SIRE").getValue());
				update.put("SIRE", new UpdateHistory(String.valueOf(sire)));
			}
			
			if(updateBefore("DAME",changes)){
				dame=Long.parseLong(changes.update.get("DAME").getValue());
				update.put("DAME", new UpdateHistory(String.valueOf(dame)));
			}
			
			if(updateBefore("ISACTIVE",changes)){
				isActive=Boolean.parseBoolean(changes.update.get("ISACTIVE").getValue());
				update.put("ISACTIVE", new UpdateHistory(String.valueOf(isActive)));
			}
			
			if(updateBefore("SAFESLAUGHTER",changes)){
				safeSlaughterDate=Long.parseLong(changes.update.get("SAFESLAUGHTER").getValue());
				update.put("SAFESLAUGHTER", new UpdateHistory(String.valueOf(safeSlaughterDate)));
			}
			
			if(updateBefore("BELONGINGMOB",changes)){
				belongingMob=Long.parseLong(changes.update.get("BELONGINGMOB").getValue());
				update.put("BELONGINGMOB", new UpdateHistory(String.valueOf(belongingMob)));
			}
			
			// Object Pass Through
			if(eartag!=null){
				eartag.update(changes.eartag);
			}else if(changes.eartag!=null){
				eartag=changes.eartag;
			}
			if(acquisition!=null){
				acquisition.update(changes.acquisition);
			}else if(changes.acquisition!=null){
				acquisition=changes.acquisition;
			}
			if(deacquisition!=null){
				deacquisition.update(changes.deacquisition);
			}else if(changes.deacquisition!=null){
				deacquisition=changes.deacquisition;
			}
			
			// Associative ArrayLists (Only Long ID)
			// Add Observations that may be new
			if(changes.update.get("OBSERVATION")!=null){
				for(String key : changes.update.get("OBSERVATION").toKeyArray()){
					if(update.get("OBSERVATION")==null || update.get("OBSERVATION").getValue(key)==null || !update.get("OBSERVATION").getValue(key).getDate().after(changes.update.get("OBSERVATION").getValue(key).getDate())){
						UpdateHistory observationList = update.get("OBSERVATION")!=null?update.get("OBSERVATION"):new UpdateHistory();
						if(changes.update.get("OBSERVATION").getValue(key).equals("false")){
							// Remove
							observations.remove(Long.parseLong(key));
							observationList.setValues(key,new UpdateHistory("false"));
						}else{
							// Add
							if(!observations.contains(Long.parseLong(key))){
								observations.add(Long.parseLong(key));
							}
							observationList.setValues(key,new UpdateHistory("true"));
						}
						update.put("OBSERVATION", observationList);
					}
				}
			}
			
			// Add Offspring that may be new
			if(changes.update.get("OFFSPRING")!=null){
				for(String key : changes.update.get("OFFSPRING").toKeyArray()){
					if(update.get("OFFSPRING")==null || update.get("OFFSPRING").getValue(key)==null || !update.get("OFFSPRING").getValue(key).getDate().after(changes.update.get("OFFSPRING").getValue(key).getDate())){
						UpdateHistory observationList = update.get("OFFSPRING")!=null?update.get("OFFSPRING"):new UpdateHistory();
						if(!changes.update.get("OFFSPRING").getValue(key).equals("false")){
							// Remove
							offspring.remove(Long.parseLong(key));
							observationList.setValues(key,new UpdateHistory("false"));
						}else{
							// Add
							offspring.add(Long.parseLong(key));
							observationList.setValues(key,new UpdateHistory("true"));
						}
						update.put("OFFSPRING", observationList);
					}
				}
			}
			
			// Add Joining that may be new
			if(changes.update.get("JOINING")!=null){
				for(String key : changes.update.get("JOINING").toKeyArray()){
					if(update.get("JOINING")==null || update.get("JOINING").getValue(key)==null || !update.get("JOINING").getValue(key).getDate().after(changes.update.get("JOINING").getValue(key).getDate())){
						UpdateHistory joiningList = update.get("JOINING")!=null?update.get("JOINING"):new UpdateHistory();
						if(!changes.update.get("JOINING").getValue(key).equals("false")){
							// Remove
							joinings.remove(Long.parseLong(key));
							joiningList.setValues(key,new UpdateHistory("false"));
						}else{
							// Add
							joinings.add(Long.parseLong(key));
							joiningList.setValues(key,new UpdateHistory("true"));
						}
						update.put("OFFSPRING", joiningList);
					}
				}
			}
	
			// Add a Group
			if(changes.update.get("GROUP")!=null){
				for(String key : changes.update.get("GROUP").toKeyArray()){
					if(update.get("GROUP")==null || update.get("GROUP").getValue(key)==null || !update.get("GROUP").getValue(key).getDate().after(changes.update.get("GROUP").getValue(key).getDate())){
						UpdateHistory groupList = update.get("GROUP")!=null?update.get("GROUP"):new UpdateHistory();
						if(!changes.update.get("GROUP").getValue(key).equals("false")){
							// Remove
							groups.remove(Long.parseLong(key));
							groupList.setValues(key,new UpdateHistory("false"));
						}else{
							// Add
							groups.add(Long.parseLong(key));
							groupList.setValues(key,new UpdateHistory("true"));
						}
						update.put("GROUP", groupList);
					}
				}
			}
			
			// Complex Checks
			if(changes.update.get("WEIGHING")!=null){
				for(String key : changes.update.get("WEIGHING").toKeyArray()){
					if(update.get("WEIGHING")==null || update.get("WEIGHING").getValue(key)==null || !update.get("WEIGHING").getValue(key).getDate().after(changes.update.get("WEIGHING").getValue(key).getDate())){
						UpdateHistory weightList = update.get("WEIGHING")!=null?update.get("WEIGHING"):new UpdateHistory();
						Weight w=null;
						if((w =weighings.get(Long.parseLong(key)))!=null){
							if(changes.update.get("WEIGHING").getValue(key).equals("false")){
								// Remove
								weighings.remove(w);
								weightList.setValues(key,new UpdateHistory("false"));
							}else{
								// Add
								w.update(changes.weighings.get(Long.parseLong(key)));
								weighings.put(w.getID(),w);
								weightList.setValues(key,new UpdateHistory("true"));
							}
						}else if(changes.update.get("WEIGHING").getValue(key).equals("true")){
							w=changes.weighings.get(Long.parseLong(key));
							weighings.put(w.getID(),w);
						}
						update.put("WEIGHING", weightList);
					}
				}
			}
			
			if(changes.update.get("FEED")!=null){
				for(String key : changes.update.get("FEED").toKeyArray()){
					if(update.get("FEED")==null || update.get("FEED").getValue(key)==null || !update.get("FEED").getValue(key).getDate().after(changes.update.get("FEED").getValue(key).getDate())){
						UpdateHistory feedList = update.get("FEED")!=null?update.get("FEED"):new UpdateHistory();
						FeedHistory f=null;
						if((f =handFeeds.get(Long.parseLong(key)))!=null){
							if(changes.update.get("FEED").getValue(key).equals("false")){
								// Remove
								handFeeds.remove(f);
								feedList.setValues(key,new UpdateHistory("false"));
							}else{
								// Add
								f.update(changes.handFeeds.get(key));
								handFeeds.put(f.getID(),f);
								feedList.setValues(key,new UpdateHistory("true"));
							}
						}else if(changes.update.get("FEED").getValue(key).equals("true")){
							f=changes.handFeeds.get(key);
							handFeeds.put(f.getID(),f);
						}
						update.put("FEED", feedList);
					}
				}
			}
	
			if(changes.update.get("MEDICINE")!=null){
				for(String key : changes.update.get("MEDICINE").toKeyArray()){
					if(update.get("MEDICINE")==null || update.get("MEDICINE").getValue(key)==null || !update.get("MEDICINE").getValue(key).getDate().after(changes.update.get("MEDICINE").getValue(key).getDate())){
						UpdateHistory medicineList = update.get("MEDICINE")!=null?update.get("MEDICINE"):new UpdateHistory();
						MedicineHistory m=null;
						if((m =medicines.get(Long.parseLong(key)))!=null){
							if(changes.update.get("MEDICINE").getValue(key).equals("false")){
								// Remove
								medicines.remove(m);
								medicineList.setValues(key,new UpdateHistory("false"));
							}else{
								// Add
								m.update(changes.medicines.get(key));
								medicines.put(m.getID(),m);
								medicineList.setValues(key,new UpdateHistory("true"));
							}
						}else if(changes.update.get("MEDICINE").getValue(key).equals("true")){
							m=changes.medicines.get(key);
							medicines.put(m.getID(),m);
						}
						update.put("MEDICINE", medicineList);
					}
				}
			}
			
			if(changes.update.get("MEDICAL")!=null){
				for(String key : changes.update.get("MEDICAL").toKeyArray()){
					if(update.get("MEDICAL")==null || update.get("MEDICAL").getValue(key)==null || !update.get("MEDICAL").getValue(key).getDate().after(changes.update.get("MEDICAL").getValue(key).getDate())){
						UpdateHistory medicineList = update.get("MEDICAL")!=null?update.get("MEDICAL"):new UpdateHistory();
						MedicalHistory m=null;
						if((m =medicals.get(Long.parseLong(key)))!=null){
							if(changes.update.get("MEDICAL").getValue(key).equals("false")){
								// Remove
								medicals.remove(m);
								medicineList.setValues(key,new UpdateHistory("false"));
							}else{
								// Add
								m.update(changes.medicals.get(key));
								medicals.put(m.getID(),m);
								medicineList.setValues(key,new UpdateHistory("true"));
							}
						}else if(changes.update.get("MEDICAL").getValue(key).equals("true")){
							m=changes.medicals.get(key);
							medicals.put(m.getID(),m);
						}
						update.put("MEDICAL", medicineList);
					}
				}
			}
	
			HashMap<Long,ArrayList<GroupHistory>> groupHistoryChanges = changes.groupHistory;
			Long[] keyList = groupHistory.keySet().toArray(new Long[]{});
			for(int x=0; x<groupHistory.size(); x++){
				for(int y=0; y<groupHistory.get(keyList[x]).size(); y++){
					groupHistory.get(keyList[x]).get(y).update(groupHistoryChanges.get(keyList[x]).get(0));
					groupHistoryChanges.get(keyList[x]).remove(0);
				}
				while(groupHistoryChanges.get(keyList[x]).size()>0){
					groupHistory.get(keyList[x]).add(groupHistoryChanges.get(keyList[x]).get(0));
					groupHistoryChanges.get(keyList[x]).remove(0);
				}
				groupHistoryChanges.remove(keyList[x]);
			}
			// Get any other groups that have not been added before
			keyList = groupHistoryChanges.keySet().toArray(new Long[]{});
			for(int x=0; x<keyList.length; x++){
				groupHistory.put(keyList[x], groupHistoryChanges.get(keyList[x]));
			}
			
			HashMap<Long,ArrayList<GroupHistory>> mobHistoryChanges = changes.mobHistory;
			keyList = mobHistory.keySet().toArray(new Long[]{});
			for(int x=0; x<mobHistory.size(); x++){
				for(int y=0; y<mobHistory.get(keyList[x]).size(); y++){
					mobHistory.get(keyList[x]).get(y).update(mobHistoryChanges.get(keyList[x]).get(0));
					mobHistoryChanges.get(keyList[x]).remove(0);
				}
				while(mobHistoryChanges.get(keyList[x]).size()>0){
					mobHistory.get(keyList[x]).add(mobHistoryChanges.get(keyList[x]).get(0));
					mobHistoryChanges.get(keyList[x]).remove(0);
				}
				mobHistoryChanges.remove(keyList[x]);
			}
			// Get any other mobs that have not been added before
			keyList = mobHistoryChanges.keySet().toArray(new Long[]{});
			for(int x=0; x<keyList.length; x++){
				mobHistory.put(keyList[x], mobHistoryChanges.get(keyList[x]));
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Checks to see if the modification date of the item at this position is before the changed value
	 * @param key The String key at which to compare the modification dates of the modified objects.
	 * @param changes The Bovine that represents this Bovine on another system.
	 * @return <tt>true</tt> if the old value was modified before the changed value. <tt>false</tt> otherwise.
	 */
	protected boolean updateBefore(String key,Bovine changes){
		if(this.update.get(key)==null && changes.update.get(key)!=null){
			return true;
		}else if(changes.update.get(key)==null){
			return false;
		}
		return !this.update.get(key).getDate().after(changes.update.get(key).getDate());
	}
	
	/**
	 * Get the total daily growth from the dob or when the first weighing took place to the latest weighing.
	 * @return A double of the difference in weight since dob or first weighing, or {@link java.lang.Double#NEGATIVE_INFINITY} 
	 *         if dob is not set and number of weighings is equal to or less than 1 or weighings is empty.
	 */
	public double getAverageDailyGrowth(){
		double startWeight = (getDob()!=null?getBreed().getAverageCalfBirthWeight():weighings.size()!=0?weighings.get(weighings.firstKey()).getWeight():Double.NEGATIVE_INFINITY);
		double endWeight = (weighings.size()!=0&&(getDob()!=null||weighings.size()>1)?weighings.get(weighings.lastKey()).getWeight():Double.NEGATIVE_INFINITY);
		
		if(!Double.isInfinite(startWeight)&&!Double.isInfinite(endWeight)) {
			return (endWeight-startWeight)/((weighings.get(weighings.lastKey()).getDate().getTime()-(getDob()!=null?getDob().getTime():weighings.get(weighings.firstKey()).getDate().getTime()))/1000/60/60/24);
		}
		return Double.NEGATIVE_INFINITY;
	}
	
	/**
	 * Get the average daily growth of any offspring that have any weighings associated with them.
	 * @return A double representing the daily weight growth since the animal was born, or the first
	 *          weighing if dob is not known, to the latest weighing of each Offspring, averaged between
	 *          all Offspring that {@link #getAverageDailyGrowth()} does not return {@link java.lang.Double#NEGATIVE_INFINITY}.
	 *          java.lang.Double#NEGATIVE_INFINITY is returned if all Offspring return that value.
	 */
	public double getOffspringAverageDailyGrowth(){
		double weightGain=0;
		Bovine[] offspring = getOffspring();
		if(offspring.length==0)
			return Double.NEGATIVE_INFINITY;
		int counter = 0;
		for(int i=0; i<offspring.length; i++){
			double adg = offspring[i].getAverageDailyGrowth();
			if(!Double.isInfinite(adg)){
				weightGain =+ adg;
				counter++;
			}
		}
		if(counter!=0 && weightGain!=Double.NEGATIVE_INFINITY) {
			weightGain = weightGain/counter;
		}
		return weightGain;
	}
	
	/**
	 * Get the active status of the cattle.
	 * @return String displaying the status of the cattle.
	 */
	public String getStringStatus(){
		return isActive?"Active ("+acquisition.getMethod()+")":"Inactive ("+deacquisition.getMethod()+")";
	}
	
	/**
	 * Gets the average selling price of this bovines offspring
	 * @return average selling price of offspring, returns negative infinity if no offspring exist
	 */
	public Double getOffspringAverageSellPrice()
	{
		ArrayList<Bovine> offspring = new ArrayList<Bovine>(Arrays.asList(getOffspring()));
		if(offspring.isEmpty())
			return Double.NEGATIVE_INFINITY;
		double aveSell = 0;
		int counter = 0;
		for(Bovine b: offspring)
		{
			if (b.getDeacquisition()!= null&&b.getDeacquisition().getMethod().toLowerCase().equals("sold"))
			{
				aveSell += b.getAcquisition().getPrice();
				counter++;
			}
		}
		if (counter == 0)
			return Double.NEGATIVE_INFINITY;
		return aveSell = aveSell/counter;
	}
	
	/**
	 * Returns the number of offspring sold
	 * @return counter - the number of offspring that have been sold
	 */
	public int getNoOffspringSold()
	{
		Bovine[] offspring = getOffspring();
		int counter = 0;
		for(int i = 0; i < offspring.length; i++)
		{
			if (offspring[i].getDeacquisition().getMethod().toLowerCase().equals("sold"))
					counter++;
		}
		return counter;
	}
	
	/**
	 * Returns the number of offspring still active but unsold
	 * @return counter - number of offspring still alive but not sold
	 */
	public int getNumOffspringNotSold()
	{
		Bovine[] offspring = getOffspring();
		int counter = 0;
		for(int i = 0; i < offspring.length; i++)
		{
			if (offspring[i].isActive())
					counter++;
		}
			return counter;
	}
}
